package cn.zhangfusheng.elasticsearch.register;

import cn.zhangfusheng.elasticsearch.annotation.EnablePaodingRoseElasticSearch;
import cn.zhangfusheng.elasticsearch.annotation.document.IndexDescription;
import cn.zhangfusheng.elasticsearch.constant.ElasticSearchConstant;
import cn.zhangfusheng.elasticsearch.constant.enumeration.TransferType;
import cn.zhangfusheng.elasticsearch.cycle.CreateMappingBefore;
import cn.zhangfusheng.elasticsearch.cycle.CreateMappingEnd;
import cn.zhangfusheng.elasticsearch.factory.ElasticSearchRepositoryFactoryBean;
import cn.zhangfusheng.elasticsearch.mybatis.ElasticSearchWithMybatisXmlConfig;
import cn.zhangfusheng.elasticsearch.repository.ElasticSearchRepository;
import cn.zhangfusheng.elasticsearch.scan.ElasticSearchEntityRepositoryDetail;
import cn.zhangfusheng.elasticsearch.scan.ElasticSearchEntityRepositoryFilter;
import cn.zhangfusheng.elasticsearch.scan.ElasticSearchRepositoryScanner;
import cn.zhangfusheng.elasticsearch.setting.SettingJsonConfig;
import cn.zhangfusheng.elasticsearch.transfer.TransferInfo;
import cn.zhangfusheng.elasticsearch.transfer.TransferInfoRepository;
import cn.zhangfusheng.elasticsearch.util.date.LocalDateTimeUtils;
import cn.zhangfusheng.elasticsearch.util.date.enumeration.DateFromatEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.Configuration;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ClassUtils;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author fusheng.zhang
 * @date 2022-02-24 10:53:33
 */
@Slf4j
public class PaodingRoseElasticSearchRegister implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
    private AdviceMode adviceMode;
    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<String, Object> annotationAttributes =
                importingClassMetadata.getAnnotationAttributes(EnablePaodingRoseElasticSearch.class.getName());
        if (Objects.isNull(annotationAttributes)) return;
        this.adviceMode = (AdviceMode) annotationAttributes.get("mode");
        String packageName = ClassUtils.getPackageName(importingClassMetadata.getClassName());
        Set<String> scanReposityPackages = new HashSet<>(), mybatisXmlScanPath = new HashSet<>(), settingJsonScanPath = new HashSet<>();
        if (annotationAttributes.containsKey("scanReposityPackages")) {
            String[] basePackage = (String[]) annotationAttributes.get("scanReposityPackages");
            scanReposityPackages.addAll(Arrays.stream(basePackage).filter(StringUtils::isNotBlank).collect(Collectors.toList()));
        }
        if (annotationAttributes.containsKey("mybatisXmlScanPath")) {
            String[] basePath = (String[]) annotationAttributes.get("mybatisXmlScanPath");
            mybatisXmlScanPath.addAll(Arrays.stream(basePath).filter(StringUtils::isNotBlank).collect(Collectors.toList()));
        }
        if (annotationAttributes.containsKey("settingJsonScanPath")) {
            String[] basePath = (String[]) annotationAttributes.get("settingJsonScanPath");
            settingJsonScanPath.addAll(Arrays.stream(basePath).filter(StringUtils::isNotBlank).collect(Collectors.toList()));
        }
        if (scanReposityPackages.isEmpty()) scanReposityPackages.add(packageName);
        // 初始化 mybatis 的config
        ElasticSearchWithMybatisXmlConfig elasticSearchWithMybatisXmlConfig = new ElasticSearchWithMybatisXmlConfig(mybatisXmlScanPath);
        // 初始化 setting json
        SettingJsonConfig settingJsonConfig = new SettingJsonConfig(settingJsonScanPath);
        // 扫描 Repository 并 register
        this.scanRepositoryAndRegister(registry, scanReposityPackages, elasticSearchWithMybatisXmlConfig, settingJsonConfig);
        // 处理版本迁移
        this.registerTransferInfoBeanDefinition(registry, elasticSearchWithMybatisXmlConfig);
    }

    /**
     * 扫描 Repository 并 register
     * @param registry
     * @param scanRepositoryPackages
     * @param elasticSearchWithMybatisXmlConfig
     * @param settingJsonConfig
     */
    private void scanRepositoryAndRegister(
            BeanDefinitionRegistry registry,
            Set<String> scanRepositoryPackages,
            Configuration elasticSearchWithMybatisXmlConfig,
            SettingJsonConfig settingJsonConfig) {
        // 检索出所有的repository 创建对应的 beanFactory
        ElasticSearchEntityRepositoryFilter filter = new ElasticSearchRepositoryScanner(registry).build(
                new ElasticSearchEntityRepositoryFilter(ElasticSearchRepository.class, settingJsonConfig),
                scanRepositoryPackages.toArray(new String[0]));
        // 扫描注册bean
        filter.getEntityRepositoryDetails().forEach(entityRepositoryDetail ->
                registerRepositoryBeanDefinition(registry, entityRepositoryDetail, elasticSearchWithMybatisXmlConfig));
    }

    /**
     * 注册 other repository
     * @param registry
     * @param entityRepositoryDetail
     */
    private void registerRepositoryBeanDefinition(
            BeanDefinitionRegistry registry,
            ElasticSearchEntityRepositoryDetail entityRepositoryDetail,
            Configuration elasticSearchWithMybatisXmlConfig) {
        log.debug("register elasticSearchRepository with:{}", entityRepositoryDetail.getElasticSearchRepositoryClass().getName());
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.genericBeanDefinition(ElasticSearchRepositoryFactoryBean.class)
                .addPropertyValue("entityRepositoryDetail", entityRepositoryDetail)
                .addPropertyValue("beanFactory", beanFactory)
                .addPropertyValue("adviceMode", this.adviceMode)
                .addPropertyValue("objectType", entityRepositoryDetail.getElasticSearchRepositoryClass())
                .addPropertyValue("elasticSearchWithMybatisXmlConfig", elasticSearchWithMybatisXmlConfig)
                .setLazyInit(Boolean.FALSE)
                .getBeanDefinition();
        registry.registerBeanDefinition(entityRepositoryDetail.getRepositoryBeanName(), beanDefinition);
    }

    /**
     * 注册版本迁移的ElasticSearchEntityRepositoryDetail
     * @param registry
     */
    private void registerTransferInfoBeanDefinition(
            BeanDefinitionRegistry registry, ElasticSearchWithMybatisXmlConfig elasticSearchWithMybatisXmlConfig) {
        Class<TransferInfo> entityClass = TransferInfo.class;
        IndexDescription indexDescription = new IndexDescription() {
            @Override
            public Class<? extends Annotation> annotationType() {
                return IndexDescription.class;
            }

            @Override
            public String value() {
                return ElasticSearchConstant.TRANSFER_INDEX_NAME;
            }

            @Override
            public String alias() {
                return "";
            }

            @Override
            public int version() {
                return ElasticSearchConstant.TRANSFER_INDEX_VERSION;
            }

            @Override
            public String upgradeVersion() {
                return LocalDateTimeUtils.nowTime(DateFromatEnum.YYYY_MM_DD_HH_MM_SS);
            }

            @Override
            public Class<? extends CreateMappingBefore>[] createMappingBefore() {
                return null;
            }

            @Override
            public Class<? extends CreateMappingEnd>[] createMappingEnd() {
                return null;
            }

            @Override
            public TransferType transferType() {
                return TransferType.DEFAULT;
            }
        };
        ElasticSearchEntityRepositoryDetail elasticSearchEntityRepositoryDetail = new ElasticSearchEntityRepositoryDetail(
                false, TransferInfoRepository.class, entityClass, indexDescription, null, null);
        this.registerRepositoryBeanDefinition(registry, elasticSearchEntityRepositoryDetail, elasticSearchWithMybatisXmlConfig);
    }
}
